use std::collections::BTreeMap;
use std::sync::Arc;

use crate::abstractions::data::{Data, Data::*};
use crate::protocol::task::SymbolicTask;
use crate::protocol::type_schema::TypeSchema;
use crate::protocol::type_schema::TypeSchema::*;
use crate::protocol::type_derive::*;
use crate::abstractions::task::{TaskAbstraction, TaskFunc};
use crate::abstractions::task::TaskAbstraction::*;
use crate::protocol::datatypes::SYSTEM_DATA_TYPE;
use crate::error::ServerError;

macro_rules! data_format_error {
    () => {
        Err(ServerError::DataErrorStr("internal data format error!"))
    };
}

pub fn type_can_fuse(from: &TypeSchema, to: &TypeSchema, direction: FuseDirection) -> bool {
    return subfunc(from, to, direction).is_some();
}


pub fn fuse_symbolic(a: &SymbolicTask, b: &SymbolicTask, force: bool, iofixed: IOFixed) -> Option<(SymbolicTask, FuseDirection)> {
    let fused_name = format!("({} -> {})", a.name, b.name);
    if a.output_type == b.input_type {
        return Some((SymbolicTask::new(fused_name, a.input_type.clone(), b.output_type.clone()), FuseDirection::Equal))
    } 
    if let Some((substitution, construction)) = derivefuncpub(&b.input_type, &a.output_type, force, FuseDirection::Positive) {
        match iofixed {
            IOFixed::Free | IOFixed::I => {
                let output_type = construction.resolve(&b.output_type);
                return Some((SymbolicTask::new(fused_name, a.input_type.clone(), output_type), FuseDirection::Positive))
            }
            IOFixed::O | IOFixed::IO => {
                if construction.is_super() {
                    let output_type = construction.resolve(&b.output_type);
                    return Some((SymbolicTask::new(fused_name, a.input_type.clone(), output_type), FuseDirection::Positive))
                }
            }
        }
    }
    if let Some((substitution, construction)) = derivefuncpub(&a.output_type, &b.input_type, force, FuseDirection::Negative) {
        match iofixed {
            IOFixed::Free | IOFixed::O => {
                let input_type = construction.resolve(&a.input_type);
                return Some((SymbolicTask::new(fused_name, input_type, b.output_type.clone()), FuseDirection::Negative))
            }
            IOFixed::I | IOFixed::IO => {
                if construction.is_super() {
                    let input_type = construction.resolve(&a.input_type);
                    return Some((SymbolicTask::new(fused_name, input_type, b.output_type.clone()), FuseDirection::Negative))
                }
            }
        }
    }
    None
}

pub fn fuse(a: TaskAbstraction, b: TaskAbstraction, force: bool, iofixed: IOFixed) -> Option<(TaskAbstraction, FuseDirection)> {
    let fused_name = format!("({} -> {})", a.name(), b.name());
    if a.tout() == b.tin() {
        let (input_type, a_func) = a.funq();
        let (output_type, b_func) = b.qfun();
        let novel_func = Arc::new(move |input:Data| {
            b_func(a_func(input)?)
        });
        Some((Builtin(fused_name, input_type, output_type, novel_func), FuseDirection::Equal))
    } else if let Some((substitution, construction)) = derivefuncpub(b.tin(), a.tout(), force, FuseDirection::Positive){
        let (input_type, a_func) = a.funq();
        let (output_type, b_func) = b.qfun();
        let novel_output_type = construction.resolve(&output_type);
        let novel_func = Arc::new(move |input:Data| {
            let temp = a_func.clone()(input)?;
            let temp = coerce(&substitution, temp)?;
            destruct_apply(&construction, b_func.clone(), temp)
        });
        Some((Builtin(fused_name, input_type, novel_output_type, novel_func), FuseDirection::Positive))
    } else if let Some((substitution, construction)) = derivefuncpub(a.tout(), b.tin(), force, FuseDirection::Negative){
        let (input_type, a_func) = a.funq();
        let (output_type, b_func) = b.qfun();
        let novel_input_type = construction.resolve(&input_type);
        let novel_func = Arc::new(move |input:Data| {
            let temp = destruct_apply_coerce(&construction, a_func.clone(), &substitution, input)?;
            b_func.clone()(temp)
        });
        Some((Builtin(fused_name, novel_input_type, output_type, novel_func), FuseDirection::Negative))
    } else {
        None
    }
}

type S = SubstitutionTransform;
type C = ConstructionTransform;

pub fn resolve_system_type(ts: &TypeSchema) -> TypeSchema {
    match ts {
        List(inner) => List(Box::new(resolve_system_type(inner))),
        Map(k, v) => Map(k.clone(), Box::new(resolve_system_type(v))),
        Tuple(ls) => Tuple(ls.iter().map(|x| resolve_system_type(x)).collect()),
        Opt(inner) => Opt(Box::new(resolve_system_type(inner))),
        DataType(name) => {
            SYSTEM_DATA_TYPE.explain(name).map(|x| x.clone()).unwrap_or(DataType(name.clone()))
        }
        _ => ts.clone()
    }
}

pub fn derivefuncpub(from: &TypeSchema, to: &TypeSchema, force: bool, direction: FuseDirection) -> Option<DerivationTransform> {
    if force {
        derivefunc(&resolve_system_type(from), &resolve_system_type(to), direction)
    } else {
        derivefunc(from, to, direction)
    }
}

fn derivefunc(from: &TypeSchema, to: &TypeSchema, direction: FuseDirection) -> Option<DerivationTransform> {
    if let Some(substitution) = subfunc(from, to, direction) {
        Some((substitution, C::Super))
    } else {
        match to {
            List(part) => {
                derivefunc(from, part, direction).map(|x|{
                    (x.0, C::List(Box::new(x.1)))
                })
            },
            Map(k, v) => {
                derivefunc(from, v, direction).map(|x|{
                    (x.0, C::Map(Box::new(C::from(&**k)), Box::new(x.1)))
                })
            },
            Tuple(ls) => {
                let mut ahead = vec![];
                let mut idx = 0;
                let mut flag = false;
                let mut substitution = S::Identity;
                for each in ls {
                    if flag {
                        ahead.push(C::from(each));
                    } else {
                        if let Some(x) = derivefunc(from, each, direction) {
                            substitution = x.0;
                            ahead.push(x.1);
                            flag = true;
                        } else {
                            ahead.push(C::from(each));
                        }
                    }
                    idx += 1;
                }
                if flag {
                    Some((substitution, C::Tuple(ahead)))
                } else {
                    None
                }
            },
            Opt(inner) => {
                derivefunc(from, inner, direction).map(|x|{
                    (x.0, C::Opt(Box::new(x.1)))
                })
            },
            
            _ => None
        }
    }
}

pub fn subfunc(from: &TypeSchema, to: &TypeSchema, direction: FuseDirection) -> Option<SubstitutionTransform> {
    match (from, to) {
        (Void, Void) => Some(S::Identity),
        (Bool, Bool) => Some(S::Identity),
        (Int, Int) => Some(S::Identity),
        (Float, Float) => Some(S::Identity),
        (Str, Str) => Some(S::Identity),
        (Json, Json) => Some(S::Identity),
        (DataType(name1), DataType(name2)) => {
            if name1 == name2 {
                Some(S::Identity)
            } else {
                None
            }
        }
        (Int, Float) => {
            if direction.is_negative() {
                Some(S::IntToFloat)
            } else {
                None
            }
        }
        (Float, Int) => {
            if direction.is_positive() {
                Some(S::FloatToInt)
            } else {
                None
            }
        }
        (Opt(t1), Opt(t2)) => {
            if let Some(x) = subfunc(t1, t2, direction) {
                Some(S::Opt(Box::new(x)))
            } else {
                None
            }
        }
        (Opt(t1), t2) => {
            if direction.is_positive() {
                if t2 == &Void {
                    Some(S::Identity)
                } else {
                    subfunc(t1, t2, direction).map(|x|{
                        S::IntoOption(Box::new(x))
                    })
                }
            } else {
                None
            }
        }
        (t1, Opt(t2)) => {
            if direction.is_negative() {
                if t1 == &Void {
                    Some(S::Identity)
                } else {
                    subfunc(t1, t2, direction).map(|x|{
                        S::IntoOption(Box::new(x))
                    })
                }
            } else {
                None
            }
        }
        (List(t1), List(t2)) => {
            if let Some(x) = subfunc(t1, t2, direction) {
                Some(S::List(Box::new(x)))
            } else {
                None
            }
        }
        (Map(k1, v1), Map(k2, v2)) => {
            if k1 == k2 {
                if let Some(x) = subfunc(v1, v2, direction) {
                    return Some(S::Map(Box::new(S::Identity), Box::new(x)))
                }
            }
            None
        }
        (Tuple(ls1), Tuple(ls2)) => {
            if ls1.len() != ls2.len() {
                None
            } else {
                let mut ahead = vec![];
                for (i, each) in ls1.iter().enumerate() {
                    if let Some(x) = subfunc(each, &ls2[i], direction) {
                        ahead.push(x);
                    } else {
                        return None;
                    }
                }
                Some(S::Tuple(ahead))
            }
        }
        _ => None
    }
}

pub fn coerce(s: &SubstitutionTransform, input: Data) -> Result<Data, ServerError> {
    match s {
        S::Identity => Ok(input),
        S::FloatToInt => match input {
            DataFloat(v) => Ok(DataInt(v as i64)),
            _ => data_format_error!()
        }
        S::IntToFloat => match input {
            DataInt(i) => Ok(DataFloat(i as f64)),
            _ => data_format_error!()
        }
        S::IntoOption(inner) => Ok(input),
        S::List(inner) => match input {
            DataList(l) => {
                let mut list = Vec::new();
                for each in l {
                    list.push(coerce(inner, each)?);
                }
                Ok(DataList(list))
            }
            _ => data_format_error!()
        }
        S::Map(kk, vv) => match input {
            DataMap(m) => {
                let mut map = BTreeMap::new();
                for (k, v) in m {
                    let k = coerce(kk, k)?;
                    let v = coerce(vv, v)?;
                    map.insert(k, v);
                }
                Ok(DataMap(map))
            }
            _ => data_format_error!()
        }
        S::Tuple(ls) => match input {
            DataTuple(d) => {
                if ls.len() != d.len() {
                    data_format_error!()
                } else {
                    let mut ahead = vec![];
                    let mut idx = 0;
                    for each in d {
                        ahead.push(coerce(&ls[idx], each)?);
                        idx += 1;
                    }
                    Ok(DataTuple(ahead))
                }
            }
            _ => data_format_error!()
        }
        S::Opt(inner) => match input {
            DataVoid => Ok(DataVoid),
            _ => coerce(inner, input)
        }
    }
}

fn destruct_apply(c: &ConstructionTransform, f: TaskFunc, input: Data) -> Result<Data, ServerError> {
    match c {
        C::Super => f(input),
        C::Void => Ok(input),
        C::Bool => Ok(input),
        C::Int => Ok(input),
        C::Float => Ok(input),
        C::Str => Ok(input),
        C::Json => Ok(input),
        C::DataType(name) => Ok(input),
        C::List(inner) => {
            match input {
                DataList(l) => {
                    let mut ahead = vec![];
                    for each in l {
                        ahead.push(destruct_apply(inner, f.clone(), each)?);
                    }
                    Ok(DataList(ahead))
                }
                _ => data_format_error!()
            }
        }
        C::Map(kk, vv) => {
            match input {
                DataMap(m) => {
                    let mut map = BTreeMap::new();
                    for (k, v) in m {
                        let k = destruct_apply(kk, f.clone(), k)?;
                        let v = destruct_apply(vv, f.clone(), v)?;
                        map.insert(k, v);
                    }
                    Ok(DataMap(map))
                }
                _ => data_format_error!()
            }
        }
        C::Tuple(ls) => {
            match input {
                DataTuple(d) => {
                    if ls.len() != d.len() {
                        data_format_error!()
                    } else {
                    let mut ahead = vec![];
                    let mut idx = 0;
                    for each in d {
                        ahead.push(destruct_apply(&ls[idx], f.clone(), each)?);
                            idx += 1;
                        }
                        Ok(DataTuple(ahead))
                    }
                }
                _ => data_format_error!()
            }
        }
        C::Opt(inner) => match input {
            DataVoid => Ok(DataVoid),
            _ => destruct_apply(inner, f, input)
        }
    }
}

fn destruct_apply_coerce(c: &ConstructionTransform, f: TaskFunc, s: &SubstitutionTransform, input: Data) -> Result<Data, ServerError> {
    match c {
        C::Super => coerce(s, f(input)?),
        C::Void => Ok(input),
        C::Bool => Ok(input),
        C::Int => Ok(input),
        C::Float => Ok(input),
        C::Str => Ok(input),
        C::Json => Ok(input),
        C::DataType(name) => Ok(input),
        C::List(inner) => {
            match input {
                DataList(l) => {
                    let mut ahead = vec![];
                    for each in l {
                        ahead.push(destruct_apply_coerce(inner, f.clone(), s, each)?);
                    }
                    Ok(DataList(ahead))
                }
                _ => data_format_error!()
            }
        }
        C::Map(kk, vv) => {
            match input {
                DataMap(m) => {
                    let mut map = BTreeMap::new();
                    for (k, v) in m {
                        let k = destruct_apply_coerce(kk, f.clone(), s, k)?;
                        let v = destruct_apply_coerce(vv, f.clone(), s, v)?;
                        map.insert(k, v);
                    }
                    Ok(DataMap(map))
                }
                _ => data_format_error!()
            }
        }
        C::Tuple(ls) => {
            match input {
                DataTuple(d) => {
                    if ls.len() != d.len() {
                        data_format_error!()
                    } else {
                    let mut ahead = vec![];
                    let mut idx = 0;
                    for each in d {
                        ahead.push(destruct_apply_coerce(&ls[idx], f.clone(), s, each)?);
                            idx += 1;
                        }
                        Ok(DataTuple(ahead))
                    }
                }
                _ => data_format_error!()
            }
        }
        C::Opt(inner) => match input {
            DataVoid => Ok(DataVoid),
            _ => destruct_apply_coerce(inner, f, s, input)
        }
    }
}

impl From<&TypeSchema> for ConstructionTransform {
    fn from(value: &TypeSchema) -> Self {
        match value {
            Void => ConstructionTransform::Void,
            Bool => ConstructionTransform::Bool,
            Int => ConstructionTransform::Int,
            Float => ConstructionTransform::Float,
            Str => ConstructionTransform::Str,
            Json => ConstructionTransform::Json,
            List(inner) => 
                ConstructionTransform::List(Box::new(ConstructionTransform::from(&**inner))),
            Map(key, value) => 
                ConstructionTransform::Map(Box::new(ConstructionTransform::from(&**key)), Box::new(ConstructionTransform::from(&**value))),
            Tuple(l) => {
                let mut novel: Vec<ConstructionTransform> = vec![];
                for each in l {
                    novel.push(each.into())
                }
                ConstructionTransform::Tuple(novel)
            }
            Opt(inner) => ConstructionTransform::Opt(Box::new(ConstructionTransform::from(&**inner))),
            DataType(name) => ConstructionTransform::DataType(name.clone()),
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_fuse_symbolic() {
        let a = "copy_file_path: Str -> (Str, Str, Str)".to_string();
        let b = "(test_1-> test_2): [Str] -> [Str]".to_string();
        let a = SymbolicTask::try_parse_loose(&a).unwrap();
        let b = SymbolicTask::try_parse_loose(&b).unwrap();
        let c = fuse_symbolic(&a, &b, false, IOFixed::Free);
        // let c = derivefuncpub(&b.input_type, &a.output_type, false, FuseDirection::Positive);
        println!("{:?}", c);
    }

    #[test]
    fn test_fuse_2() {
        let a = "copy_file_path: Str -> (A,Int)".to_string();
        let b = "test: (Bool, ?(FilePath, (A,?Int)), ?((FilePath, ?Int), [Int]), FilePath) -> Int".to_string();
        let a = SymbolicTask::try_parse_loose(&a).unwrap();
        let b = SymbolicTask::try_parse_loose(&b).unwrap();
        // let c = fuse_symbolic(&a, &b, false, IOFixed::Free);
        let c = derivefuncpub(&b.input_type, &a.output_type, false, FuseDirection::Positive);
        println!("{:?}", c);
        let c = derivefuncpub(&a.output_type,&b.input_type,  false, FuseDirection::Negative);
        println!("{:?}", c);
    }
}